package com.pig4cloud.pig.auth.support.password;

import com.pig4cloud.pig.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationConverter;
import com.pig4cloud.pig.common.security.util.OAuth2EndpointUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;

import java.util.Map;
import java.util.Set;

/**
 * OAuth2 资源所有者密码认证转换器
 * <p>
 * 该类继承自 OAuth2ResourceOwnerBaseAuthenticationConverter，
 * 负责将 HTTP 请求转换为密码模式的认证令牌。
 * 
 * 主要功能：
 * 1. 验证请求中的用户名和密码参数
 * 2. 构建密码模式的认证令牌
 * 3. 确保必须参数的存在和唯一性
 * 
 * OAuth2 密码模式规范：
 * - grant_type=password
 * - username：必须参数，资源所有者的用户名
 * - password：必须参数，资源所有者的密码
 * - scope：可选参数，访问范围
 *
 * @author lengleng
 * @author jumuning
 * @date 2025/05/30
 */
public class OAuth2ResourceOwnerPasswordAuthenticationConverter
		extends OAuth2ResourceOwnerBaseAuthenticationConverter<OAuth2ResourceOwnerPasswordAuthenticationToken> {

	/**
	 * 判断是否支持指定的授权类型
	 * <p>
	 * 该方法仅支持密码模式（password）。
	 * 当请求中的 grant_type 参数为 "password" 时返回 true。
	 * 
	 * @param grantType 授权类型
	 * @return 如果是密码模式返回 true，否则返回 false
	 */
	@Override
	public boolean support(String grantType) {
		return AuthorizationGrantType.PASSWORD.getValue().equals(grantType);
	}

	/**
	 * 构建 OAuth2 资源所有者密码认证令牌
	 * <p>
	 * 使用提供的参数创建一个新的密码模式认证令牌。
	 * 这个令牌将在后续的认证流程中被 Provider 处理。
	 * 
	 * @param clientPrincipal 客户端主体认证信息，包含已认证的客户端信息
	 * @param requestedScopes 请求的作用域集合，定义访问权限范围
	 * @param additionalParameters 附加参数映射，包含 username、password 等信息
	 * @return 构建完成的 OAuth2 资源所有者密码认证令牌
	 */
	@Override
	public OAuth2ResourceOwnerPasswordAuthenticationToken buildToken(Authentication clientPrincipal,
			Set requestedScopes, Map additionalParameters) {
		return new OAuth2ResourceOwnerPasswordAuthenticationToken(AuthorizationGrantType.PASSWORD, clientPrincipal,
				requestedScopes, additionalParameters);
	}

	/**
	 * 校验扩展参数
	 * <p>
	 * 根据 OAuth2 规范，密码模式必须提供以下参数：
	 * 1. username：资源所有者的用户名（必须）
	 * 2. password：资源所有者的密码（必须）
	 * 
	 * 校验规则：
	 * - 参数不能为空
	 * - 每个参数只能出现一次（防止参数注入）
	 * - 如果校验失败，抛出 OAuth2 错误
	 * 
	 * @param request HTTP 请求对象，包含所有请求参数
	 * @throws OAuth2AuthenticationException 当参数缺失或无效时抛出
	 */
	@Override
	public void checkParams(HttpServletRequest request) {
		// 获取请求参数
		MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
		
		// 校验用户名参数（必须）
		// 用户名不能为空且只能出现一次
		String username = parameters.getFirst(OAuth2ParameterNames.USERNAME);
		if (!StringUtils.hasText(username) || parameters.get(OAuth2ParameterNames.USERNAME).size() != 1) {
			OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.USERNAME,
					OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
		}

		// 校验密码参数（必须）
		// 密码不能为空且只能出现一次
		String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD);
		if (!StringUtils.hasText(password) || parameters.get(OAuth2ParameterNames.PASSWORD).size() != 1) {
			OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.PASSWORD,
					OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
		}
	}

}
